iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Software Development

掌握Java神器,駕馭SpringBoot猛獸系列 第 13

第十三日 Java動態載入與IOC元件

  • 分享至 

  • xImage
  •  

第13天,昨天講到了代理模式的實現方式,本章會講解兩個重點,利用Java動態載入類別,以及實作簡單的IOC元件,實現不使用 import 的情況下取得依賴元件

Java動態載入類別

Java 的原生類別 Class提供了透過使用類別路徑,載入類別的方式Class.forName("完整類別路徑"),話不多說上程式碼示範使用方式

案例一:先測試載入不存在的類別

try {
    // 透過 Class類別加載類別
    Class<?> clazz = Class.forName("com.example.NoExistsClass");
    Constructor<?> constructor = clazz.getConstructor(); // 无参构造函数
    // 執行建構子取得實例
    Object instance = constructor.newInstance();
} catch (Exception ex) {
    System.out.println(ex.getClass());
    System.out.println(ex.getMessage());
}

運行後會打印出找不到類別的錯誤

案例二:載入存在的類別

建立新類別 Sample.java

public class Sample {
    private String name = "Default Value";

    public void printName() {
        System.out.print(name);
    }

    public String getName() {
        return name;
    }
}
try {
    // 透過 Class類別加載類別,類別路徑可依實際環境調整
    Class clazz = Class.forName("app.other.Sample"); 
   
    //用原生Class類別取得建構子方法
    Constructor constructor = clazz.getConstructor();

    // 執行建構子取得實例
    Object instance = constructor.newInstance();
    System.out.printf("Instance Class %s", instance.getClass());
} catch (Exception ex) {
    System.out.println(ex.getClass());
    System.out.println(ex.getMessage());
}

執行 Main.java 打印出「Instance Class class app.other.Sample」字符串,上面的範例演示了,在程式執行時載入類別的方式,可以將要載入的類別路徑套用到Class.forName方法中,並透過 Constructor類別取得建構器,在執行newInstance方法在運行時建構類別實例,接著將程式碼封裝成一個方法,修改後的結果如下

在 Main.java定義靜態方法 classFactory

static Object classFactory(String classPath) {
try {
        // 透過 Class類別加載類別
        Class clazz = Class.forName(classPath); Constructor constructor = clazz.getConstructor(); // 无参构造函数

        // 執行建構子取得實例
        return constructor.newInstance();
    } catch (Exception ex) {
        System.out.println(ex.getClass());
        System.out.println(ex.getMessage());
    }
}

做一個IOC元件

將類別載入的程式碼封裝好了以後,在建立一個IOC類別,來實作一個簡易版本的IOC容器,

class IoCBox {
    private Map<String, Object> instances = new HashMap<>();

    // 新增 key
    public void put(String key, Object instance) {
        instances.put(key, instance);
    }

    // 取得實例
    public Object get(String key) {
        Object instance = instances.get(key);
        if (instance == null) {
            instance = classFactory(key);
            this.put(key, instance);
        }

        return instance;
    }

    // 移除容納的實例
    public void remove(String classPath) {
        instances.remove(classPath);
    }
}

在 Main方法調用 IoCBox

public static void main(String[] args) {
    IoCBox ioc = new IoCBox();
    Object obj = ioc.get("app.other.Sample");
    System.out.printf("IoC Container get instance %s", obj.getClass());
}

透過封裝好的IOC容器,可以將使用到的元件交由IoCBox保管,需要調用封裝好的方法,輸入類別路徑透過容器動態取得需要使用的實例,目前只有實現取得實例的業務邏輯,為了完美詮釋依賴性注入原則,還需要一些修改

調整get方法實作邏輯,注意為了方便展示利用IOC修改屬性的實作,此範例針對 Sample的name屬性修改新數值

public Object get(String key) {

    Object instance = instances.get(key);
    if (instance == null) {
    instance = classFactory(key);

    // 取得實例內的所有屬性(含私有屬性)
    Field[] fields = instance.getClass().getDeclaredFields();
    for (Field field : fields) {
        try {
            // 設定開放修改私有類型
            field.setAccessible(true);
            field.set(instance, "NewValue");
        } catch (Exception ex) {
            System.out.printf("Change Field Error %s", ex.getMessage());
        }
    }
        this.put(key, instance);
    }
    return instance;
}

調整 main方法實作

public static void main(String[] args) {
    IocContainer ioc = new IocContainer();
    Sample obj1 = new Sample();
    System.out.printf("Fist %s\n", obj1.getName());
    Sample obj2 = (Sample) ioc.get("app.other.Sample");
    System.out.printf("Using Ioc %s\n", obj2.getName());
}

執行程式運行 obj1.getName 那行打印出 Default Value
接著在obje2.getName那行可以發現,Sample的name值在IOC取得實例時,修改了私有屬性,因此實際打印會是不同值

參考資料

  1. Java實現動態載入

上一篇
第十二日 看代理模式
下一篇
第14日 來看常見的資料物件
系列文
掌握Java神器,駕馭SpringBoot猛獸30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言